package cn.net.xyan.tts.sapi;

import cn.net.xyan.tts.api.ISynthesizer;
import cn.net.xyan.tts.api.WaveFormat;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.SafeArray;
import com.jacob.com.Variant;
import org.scijava.nativelib.NativeLibraryUtil;

import javax.sound.sampled.AudioInputStream;

/**
 * Created by zarra on 2017/1/25.
 */
public class Synthesizer implements ISynthesizer {
    public static final int SPF_DEFAULT = 0;
    public static final int SPF_ASYNC = 1;
    public static final int SPF_PURGEBEFORESPEAK = 2;
    public static final int SPF_IS_FILENAME = 4;
    public static final int SPF_IS_XML = 8;
    public static final int SPF_IS_NOT_XML = 0x10;
    public static final int SPF_PERSIST_XML = 0x20;
    public static final int SPF_NLP_SPEAK_PUNC = 0x40;
    public static final int SPF_NLP_MASK = SPF_NLP_SPEAK_PUNC;
    public static final int SPF_VOICE_MASK = (((((SPF_ASYNC | SPF_PURGEBEFORESPEAK) | SPF_IS_FILENAME) | SPF_IS_XML) | SPF_IS_NOT_XML) | SPF_NLP_MASK) | SPF_PERSIST_XML;
    public static final int SPF_UNUSED_FLAGS = ~SPF_VOICE_MASK;

    final static String ProgIDSpVoice = "SAPI.SpVoice";
    final static String ProgIDSpMemoryStream = "SAPI.SpMemoryStream";
    final static String IID_ISpeechVoice = "{269316d8-57bd-11d2-9eee-00c04f797396}";

    ActiveXComponent spVoice;
    ActiveXComponent spStream;
    Dispatch speechVoice;

    public Synthesizer() {
        NativeLibraryUtil.Architecture arch = NativeLibraryUtil.getArchitecture();

        if (arch == NativeLibraryUtil.Architecture.WINDOWS_32 || arch == NativeLibraryUtil.Architecture.WINDOWS_64) {

            spVoice = new ActiveXComponent(ProgIDSpVoice);
            spStream = new ActiveXComponent(ProgIDSpMemoryStream);
            speechVoice = spVoice.QueryInterface(IID_ISpeechVoice);
            Dispatch.putRef(speechVoice, "AudioOutputStream", spStream);
        }else{
            throw new UnsupportedOperationException("SAPI must run at Microsoft Windows OS!");
        }
    }

    public void speak(String word, Integer option) {
        int o = SPF_DEFAULT;
        if (option != null)
            o = option;
        Variant vo = new Variant(o);
        Variant vWord = new Variant(word);

        Dispatch.call(speechVoice, "Speak", vWord, vo);
    }

    public byte[] audioData() {
        Variant data = Dispatch.call(spStream, "GetData");
        SafeArray safeArray = data.toSafeArray();

        int l = safeArray.getLBound(1);
        int u = safeArray.getUBound(1);
        int size = u - l + 1;

        byte[] javaData = new byte[size];
        for (int i = 0; i < size; i++) {
            javaData[i] = 0;
        }
        safeArray.getBytes(0, size, javaData, 0);

        return javaData;
    }

    @Override
    public void speak(String word, Object option) {
        Integer o = null;
        if (option!=null && option instanceof Integer){
            o = (Integer) option;
        }
        speak(word,o);
    }

    public AudioInputStream audioStream() {
        return getWaveFormat().createAudioInputStream(audioData());
    }

    public WaveFormat getWaveFormat() {
        WaveFormat format = new WaveFormat();

        Dispatch spAudioFormat = Dispatch.get(spStream, "Format").toDispatch();

        Dispatch waveFormatEx = Dispatch.call(spAudioFormat, "GetWaveFormatEx").toDispatch();

        Integer avgBytesPerSec = (Integer) Dispatch.get(waveFormatEx, "AvgBytesPerSec").toJavaObject();
        Short bitPerSample = (Short) Dispatch.get(waveFormatEx, "BitsPerSample").toJavaObject();
        Integer samplesPerSec = (Integer) Dispatch.get(waveFormatEx, "SamplesPerSec").toJavaObject();

        short blockAlign = (Short) Dispatch.get(waveFormatEx, "BlockAlign").toJavaObject();
        short channels = (Short) Dispatch.get(waveFormatEx, "Channels").toJavaObject();
        short formatTag = (Short) Dispatch.get(waveFormatEx, "FormatTag").toJavaObject();

        format.setAvgBytesPerSec(avgBytesPerSec);
        format.setBitsPerSample(bitPerSample);
        format.setSamplesPerSec(samplesPerSec);
        format.setBlockAlign(blockAlign);
        format.setChannels(channels);
        format.setFormatTag(formatTag);

        return format;
    }

    public int getRate() {
        return (Integer) Dispatch.get(speechVoice, "Rate").toJavaObject();
    }

    public void setRate(int rate) {
        Dispatch.put(speechVoice, "Rate", new Variant(rate));
    }

    public int getVolume() {
        return (Integer) Dispatch.get(speechVoice, "Volume").toJavaObject();
    }

    public void setVolume(int volume) {
        Dispatch.put(speechVoice, "Volume", new Variant(volume));
    }
}
